<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>SonarQube数据可视化</title>
    <script src="https://cdn.jsdelivr.net/npm/vue@2.7.14/dist/vue.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/axios@1.4.0/dist/axios.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js"></script>
    <link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
    <style>
        body {
            font-family: 'Arial', sans-serif;
            margin: 0;
            padding: 0;
            background-color: #f5f7fa;
        }
        .container {
            max-width: 1400px;
            margin: 0 auto;
            padding: 20px;
        }
        .card {
            background-color: white;
            border-radius: 8px;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05);
            margin-bottom: 20px;
            padding: 20px;
        }
        .grid {
            display: grid;
            grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
            gap: 20px;
        }
        .stats-card {
            padding: 15px;
            border-radius: 6px;
            color: white;
        }
        .stats-card h3 {
            margin: 0;
            font-size: 24px;
        }
        .stats-card p {
            margin: 5px 0 0 0;
            font-size: 14px;
            opacity: 0.8;
        }
        .bg-primary { background-color: #165DFF; }
        .bg-success { background-color: #00B42A; }
        .bg-warning { background-color: #FF7D00; }
        .bg-danger { background-color: #F53F3F; }
        .bg-info { background-color: #86909C; }
        .table {
            width: 100%;
            border-collapse: collapse;
        }
        .table th, .table td {
            padding: 10px;
            border-bottom: 1px solid #eee;
            text-align: left;
        }
        .table th {
            background-color: #f8f9fa;
            font-weight: 600;
        }
        .btn {
            padding: 8px 15px;
            border-radius: 4px;
            border: none;
            cursor: pointer;
            font-size: 14px;
            transition: background-color 0.3s;
        }
        .btn-primary {
            background-color: #165DFF;
            color: white;
        }
        .btn-primary:hover {
            background-color: #0e4ad2;
        }
        .form-control {
            padding: 8px 12px;
            border: 1px solid #ddd;
            border-radius: 4px;
            font-size: 14px;
            width: 100%;
            box-sizing: border-box;
        }
        .select-control {
            padding: 8px 12px;
            border: 1px solid #ddd;
            border-radius: 4px;
            font-size: 14px;
            width: 100%;
            box-sizing: border-box;
            background-color: white;
        }
        .chart-container {
            height: 300px;
        }
        .loading {
            text-align: center;
            padding: 50px;
            font-size: 18px;
            color: #666;
        }
        .error {
            text-align: center;
            padding: 50px;
            font-size: 18px;
            color: #F53F3F;
        }
        .nav-tabs {
            display: flex;
            border-bottom: 1px solid #eee;
            margin-bottom: 20px;
        }
        .nav-tab {
            padding: 10px 15px;
            cursor: pointer;
            border-bottom: 2px solid transparent;
            transition: border-color 0.3s;
        }
        .nav-tab.active {
            border-bottom-color: #165DFF;
            font-weight: 600;
        }
        .badge {
            display: inline-block;
            padding: 3px 8px;
            border-radius: 12px;
            font-size: 12px;
            font-weight: 500;
        }
        .badge-danger { background-color: #F53F3F; color: white; }
        .badge-warning { background-color: #FF7D00; color: white; }
        .badge-info { background-color: #86909C; color: white; }
        .badge-success { background-color: #00B42A; color: white; }
        .badge-primary { background-color: #165DFF; color: white; }
        .pagination {
            display: flex;
            justify-content: center;
            margin-top: 20px;
        }
        .page-item {
            list-style: none;
            margin: 0 5px;
        }
        .page-link {
            padding: 8px 12px;
            border: 1px solid #ddd;
            border-radius: 4px;
            cursor: pointer;
        }
        .page-link.active {
            background-color: #165DFF;
            color: white;
            border-color: #165DFF;
        }
    </style>
</head>
<body>
<div id="app">
    <div class="container">
        <header class="mb-6">
            <h1 class="text-2xl font-bold text-gray-800">SonarQube数据可视化</h1>
            <p class="text-gray-600">实时监控项目代码质量和安全性</p>
        </header>

        <!-- 筛选条件 -->
        <div class="card">
            <h3 class="text-lg font-semibold mb-4">筛选条件</h3>
            <div class="grid">
                <div>
                    <label class="block text-sm font-medium text-gray-700 mb-1">项目</label>
                    <select v-model="selectedProject" class="select-control">
                        <option value="">全部项目</option>
                        <option v-for="project in projects" :value="project.key">{{ project.name }}</option>
                    </select>
                </div>
                <div>
                    <label class="block text-sm font-medium text-gray-700 mb-1">时间范围</label>
                    <select v-model="timeRange" class="select-control">
                        <option value="7">最近7天</option>
                        <option value="30">最近30天</option>
                        <option value="90">最近90天</option>
                    </select>
                </div>
                <div>
                    <label class="block text-sm font-medium text-gray-700 mb-1">问题类型</label>
                    <select v-model="issueType" class="select-control">
                        <option value="">全部类型</option>
                        <option value="BUG">缺陷</option>
                        <option value="VULNERABILITY">漏洞</option>
                        <option value="CODE_SMELL">代码异味</option>
                        <option value="SECURITY_HOTSPOT">安全热点</option>
                    </select>
                </div>
                <div>
                    <label class="block text-sm font-medium text-gray-700 mb-1">严重程度</label>
                    <select v-model="severity" class="select-control">
                        <option value="">全部级别</option>
                        <option value="BLOCKER">阻塞</option>
                        <option value="CRITICAL">严重</option>
                        <option value="MAJOR">主要</option>
                        <option value="MINOR">次要</option>
                        <option value="INFO">信息</option>
                    </select>
                </div>
            </div>
            <div class="mt-4 text-right">
                <button @click="fetchData" class="btn btn-primary">
                    <i class="fa fa-refresh mr-1"></i> 刷新数据
                </button>
            </div>
        </div>

        <!-- 统计卡片 -->
        <div class="grid">
            <div class="stats-card bg-primary">
                <h3>{{ overview.totalIssues }}</h3>
                <p>总问题数</p>
            </div>
            <div class="stats-card bg-danger">
                <h3>{{ overview.blockerCritical }}</h3>
                <p>阻塞/严重问题</p>
            </div>
            <div class="stats-card bg-warning">
                <h3>{{ overview.techDebt }}h</h3>
                <p>技术债</p>
            </div>
            <div class="stats-card bg-success">
                <h3>{{ overview.coverage }}%</h3>
                <p>代码覆盖率</p>
            </div>
        </div>

        <!-- 图表区域 -->
        <div class="grid grid-cols-1 lg:grid-cols-2">
            <div class="card">
                <h3 class="text-lg font-semibold mb-3">问题类型分布</h3>
                <div class="chart-container">
                    <canvas id="issueTypesChart"></canvas>
                </div>
            </div>
            <div class="card">
                <h3 class="text-lg font-semibold mb-3">严重程度分布</h3>
                <div class="chart-container">
                    <canvas id="severityChart"></canvas>
                </div>
            </div>
            <div class="card lg:col-span-2">
                <h3 class="text-lg font-semibold mb-3">问题趋势</h3>
                <div class="chart-container">
                    <canvas id="trendChart"></canvas>
                </div>
            </div>
        </div>

        <!-- 问题列表 -->
        <div class="card">
            <div class="flex justify-between items-center mb-4">
                <h3 class="text-lg font-semibold">问题列表</h3>
                <div class="relative w-64">
                    <input
                            type="text"
                            v-model="searchQuery"
                            placeholder="搜索问题..."
                            class="form-control pl-9"
                    >
                    <i class="fa fa-search absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"></i>
                </div>
            </div>

            <div class="overflow-x-auto">
                <table class="table">
                    <thead>
                    <tr>
                        <th>问题</th>
                        <th>类型</th>
                        <th>严重程度</th>
                        <th>文件</th>
                        <th>代码行</th>
                        <th>状态</th>
                        <th>操作</th>
                    </tr>
                    </thead>
                    <tbody>
                    <tr v-for="issue in filteredIssues" :key="issue.key">
                        <td>
                            <div class="flex items-center">
                                <i
                                        :class="['fa', issueTypeIcon(issue.type), 'mr-2',
                                                issue.type === 'BUG' ? 'text-danger' :
                                                issue.type === 'VULNERABILITY' ? 'text-warning' :
                                                issue.type === 'CODE_SMELL' ? 'text-info' : 'text-primary']"
                                ></i>
                                <span>{{ issue.message }}</span>
                            </div>
                        </td>
                        <td>
                                    <span class="badge"
                                          :class="issue.type === 'BUG' ? 'badge-danger' :
                                               issue.type === 'VULNERABILITY' ? 'badge-warning' :
                                               issue.type === 'CODE_SMELL' ? 'badge-info' : 'badge-primary'">
                                        {{ issue.type }}
                                    </span>
                        </td>
                        <td>
                                    <span class="badge"
                                          :class="issue.severity === 'BLOCKER' ? 'badge-danger' :
                                               issue.severity === 'CRITICAL' ? 'badge-warning' :
                                               issue.severity === 'MAJOR' ? 'badge-orange' :
                                               issue.severity === 'MINOR' ? 'badge-info' : 'badge-primary'">
                                        {{ issue.severity }}
                                    </span>
                        </td>
                        <td>{{ issue.component }}</td>
                        <td>{{ issue.line }}</td>
                        <td>
                                    <span class="badge"
                                          :class="issue.status === 'OPEN' ? 'badge-warning' :
                                               issue.status === 'RESOLVED' ? 'badge-success' : 'badge-info'">
                                        {{ issue.status }}
                                    </span>
                        </td>
                        <td>
                            <button class="btn btn-primary btn-sm" @click="viewIssue(issue)">
                                <i class="fa fa-eye"></i>
                            </button>
                        </td>
                    </tr>
                    </tbody>
                </table>
            </div>

            <!-- 分页 -->
            <div class="pagination">
                <li class="page-item" :class="{ disabled: currentPage === 1 }">
                    <a class="page-link" @click="prevPage" href="javascript:void(0)">上一页</a>
                </li>
                <li class="page-item" v-for="page in totalPages" :key="page" :class="{ active: currentPage === page }">
                    <a class="page-link" @click="goToPage(page)" href="javascript:void(0)">{{ page }}</a>
                </li>
                <li class="page-item" :class="{ disabled: currentPage === totalPages }">
                    <a class="page-link" @click="nextPage" href="javascript:void(0)">下一页</a>
                </li>
            </div>
        </div>
    </div>

    <!-- 问题详情模态框 -->
    <div v-if="showIssueModal" class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50">
        <div class="bg-white rounded-lg shadow-xl w-full max-w-2xl max-h-[80vh] overflow-auto">
            <div class="p-5 border-b">
                <div class="flex justify-between items-center">
                    <h3 class="text-xl font-bold">问题详情</h3>
                    <button @click="showIssueModal = false" class="text-gray-500 hover:text-gray-700">
                        <i class="fa fa-times"></i>
                    </button>
                </div>
            </div>
            <div class="p-5">
                <div class="mb-4">
                    <h4 class="font-semibold mb-2">问题描述</h4>
                    <p>{{ selectedIssue.message }}</p>
                </div>
                <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
                    <div>
                        <h4 class="font-semibold mb-1">类型</h4>
                        <p>{{ selectedIssue.type }}</p>
                    </div>
                    <div>
                        <h4 class="font-semibold mb-1">严重程度</h4>
                        <p>{{ selectedIssue.severity }}</p>
                    </div>
                    <div>
                        <h4 class="font-semibold mb-1">文件</h4>
                        <p>{{ selectedIssue.component }}</p>
                    </div>
                    <div>
                        <h4 class="font-semibold mb-1">代码行</h4>
                        <p>{{ selectedIssue.line }}</p>
                    </div>
                    <div>
                        <h4 class="font-semibold mb-1">状态</h4>
                        <p>{{ selectedIssue.status }}</p>
                    </div>
                    <div>
                        <h4 class="font-semibold mb-1">创建时间</h4>
                        <p>{{ selectedIssue.creationDate }}</p>
                    </div>
                </div>
                <div>
                    <h4 class="font-semibold mb-2">代码片段</h4>
                    <pre class="bg-gray-100 p-3 rounded overflow-auto max-h-40">{{ selectedIssue.codeSnippet }}</pre>
                </div>
            </div>
            <div class="p-5 border-t">
                <button @click="showIssueModal = false" class="btn btn-primary">关闭</button>
            </div>
        </div>
    </div>
</div>

<script>
    new Vue({
        el: '#app',
        data: {
            projects: [],
            selectedProject: '',
            timeRange: '7',
            issueType: '',
            severity: '',
            searchQuery: '',
            currentPage: 1,
            itemsPerPage: 10,
            overview: {
                totalIssues: 0,
                blockerCritical: 0,
                techDebt: 0,
                coverage: 0
            },
            issues: [],
            issueTypesData: {},
            severityData: {},
            trendData: { dates: [], values: [] },
            showIssueModal: false,
            selectedIssue: {},
            loading: false,
            error: null,
            apiBaseUrl: 'http://localhost:8080/api/sonarqube' // 后端API基础URL
        },
        computed: {
            // 过滤并分页后的问题列表
            filteredIssues() {
                const search = this.searchQuery.toLowerCase();
                const filtered = this.issues.filter(issue =>
                    issue.message.toLowerCase().includes(search) ||
                    issue.component.toLowerCase().includes(search) ||
                    issue.type.toLowerCase().includes(search) ||
                    issue.severity.toLowerCase().includes(search)
                );

                const start = (this.currentPage - 1) * this.itemsPerPage;
                const end = start + this.itemsPerPage;
                return filtered.slice(start, end);
            },
            // 总页数
            totalPages() {
                return Math.ceil(this.issues.length / this.itemsPerPage);
            }
        },
        methods: {
            // 获取项目列表
            async fetchProjects() {
                try {
                    const response = await axios.get(`${this.apiBaseUrl}/projects`);
                    this.projects = response.data;
                } catch (error) {
                    this.error = '获取项目列表失败';
                    console.error('获取项目列表失败:', error);
                }
            },

            // 获取数据
            async fetchData() {
                this.loading = true;
                this.error = null;

                try {
                    // 获取项目概览数据
                    const overviewResponse = await axios.get(`${this.apiBaseUrl}/overview`, {
                        params: {
                            project: this.selectedProject,
                            days: this.timeRange
                        }
                    });
                    this.overview = overviewResponse.data;

                    // 获取问题类型分布
                    const issueTypesResponse = await axios.get(`${this.apiBaseUrl}/issue-types`, {
                        params: {
                            project: this.selectedProject,
                            days: this.timeRange
                        }
                    });
                    this.issueTypesData = issueTypesResponse.data;

                    // 获取严重程度分布
                    const severityResponse = await axios.get(`${this.apiBaseUrl}/severity`, {
                        params: {
                            project: this.selectedProject,
                            days: this.timeRange
                        }
                    });
                    this.severityData = severityResponse.data;

                    // 获取问题趋势
                    const trendResponse = await axios.get(`${this.apiBaseUrl}/trend`, {
                        params: {
                            project: this.selectedProject,
                            days: this.timeRange
                        }
                    });
                    this.trendData = trendResponse.data;

                    // 获取问题列表
                    const issuesResponse = await axios.get(`${this.apiBaseUrl}/issues`, {
                        params: {
                            project: this.selectedProject,
                            type: this.issueType,
                            severity: this.severity,
                            days: this.timeRange
                        }
                    });
                    this.issues = issuesResponse.data;

                    // 更新图表
                    this.updateCharts();
                } catch (error) {
                    this.error = '获取数据失败';
                    console.error('获取数据失败:', error);
                } finally {
                    this.loading = false;
                }
            },

            // 更新图表
            updateCharts() {
                // 问题类型饼图
                this.createIssueTypesChart();

                // 严重程度饼图
                this.createSeverityChart();

                // 趋势线图
                this.createTrendChart();
            },

            // 创建问题类型图表
            createIssueTypesChart() {
                const ctx = document.getElementById('issueTypesChart').getContext('2d');

                if (window.issueTypesChart) {
                    window.issueTypesChart.destroy();
                }

                window.issueTypesChart = new Chart(ctx, {
                    type: 'doughnut',
                    data: {
                        labels: ['缺陷', '漏洞', '代码异味', '安全热点'],
                        datasets: [{
                            data: [
                                this.issueTypesData.BUG || 0,
                                this.issueTypesData.VULNERABILITY || 0,
                                this.issueTypesData.CODE_SMELL || 0,
                                this.issueTypesData.SECURITY_HOTSPOT || 0
                            ],
                            backgroundColor: [
                                '#F53F3F', // 红色 - 缺陷
                                '#FF7D00', // 橙色 - 漏洞
                                '#86909C', // 灰色 - 代码异味
                                '#165DFF'  // 蓝色 - 安全热点
                            ],
                            borderWidth: 0
                        }]
                    },
                    options: {
                        responsive: true,
                        maintainAspectRatio: false,
                        plugins: {
                            legend: {
                                position: 'bottom'
                            }
                        },
                        cutout: '70%'
                    }
                });
            },

            // 创建严重程度图表
            createSeverityChart() {
                const ctx = document.getElementById('severityChart').getContext('2d');

                if (window.severityChart) {
                    window.severityChart.destroy();
                }

                window.severityChart = new Chart(ctx, {
                    type: 'doughnut',
                    data: {
                        labels: ['阻塞', '严重', '主要', '次要', '信息'],
                        datasets: [{
                            data: [
                                this.severityData.BLOCKER || 0,
                                this.severityData.CRITICAL || 0,
                                this.severityData.MAJOR || 0,
                                this.severityData.MINOR || 0,
                                this.severityData.INFO || 0
                            ],
                            backgroundColor: [
                                '#F53F3F', // 红色 - 阻塞
                                '#FF7D00', // 橙色 - 严重
                                '#FFAA00', // 黄色 - 主要
                                '#86909C', // 灰色 - 次要
                                '#C9CDD4'  // 浅灰 - 信息
                            ],
                            borderWidth: 0
                        }]
                    },
                    options: {
                        responsive: true,
                        maintainAspectRatio: false,
                        plugins: {
                            legend: {
                                position: 'bottom'
                            }
                        },
                        cutout: '70%'
                    }
                });
            },

            // 创建趋势图表
            createTrendChart() {
                const ctx = document.getElementById('trendChart').getContext('2d');

                if (window.trendChart) {
                    window.trendChart.destroy();
                }

                window.trendChart = new Chart(ctx, {
                    type: 'line',
                    data: {
                        labels: this.trendData.dates,
                        datasets: [{
                            label: '问题总数',
                            data: this.trendData.values,
                            borderColor: '#165DFF',
                            backgroundColor: 'rgba(22, 93, 255, 0.1)',
                            fill: true,
                            tension: 0.4
                        }]
                    },
                    options: {
                        responsive: true,
                        maintainAspectRatio: false,
                        plugins: {
                            legend: {
                                display: false
                            }
                        },
                        scales: {
                            y: {
                                beginAtZero: true
                            }
                        }
                    }
                });
            },

            // 获取问题类型图标
            issueTypeIcon(type) {
                switch(type) {
                    case 'BUG': return 'bug';
                    case 'VULNERABILITY': return 'shield';
                    case 'CODE_SMELL': return 'exclamation-triangle';
                    case 'SECURITY_HOTSPOT': return 'fire';
                    default: return 'question-circle';
                }
            },

            // 分页相关方法
            prevPage() {
                if (this.currentPage > 1) {
                    this.currentPage--;
                }
            },
            nextPage() {
                if (this.currentPage < this.totalPages) {
                    this.currentPage++;
                }
            },
            goToPage(page) {
                this.currentPage = page;
            },

            // 查看问题详情
            viewIssue(issue) {
                this.selectedIssue = issue;
                this.showIssueModal = true;
            }
        },
        mounted() {
            // 初始化加载数据
            this.fetchProjects();
            this.fetchData();
        }
    });
</script>
</body>
</html>